package ai.timefold.solver.core.impl.domain.common.accessor.gizmo;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;

import java.lang.reflect.Member;
import java.lang.reflect.Method;

import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
import ai.timefold.solver.core.testdomain.TestdataEntity;
import ai.timefold.solver.core.testdomain.TestdataValue;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class GizmoMemberAccessorFactoryTest {

    static ClassLoader contextClassLoader;

    @BeforeAll
    static void setContextClassLoader() {
        contextClassLoader = Thread.currentThread().getContextClassLoader();
    }

    @BeforeEach
    void setup() {
        Thread.currentThread().setContextClassLoader(contextClassLoader);
    }

    // Duplicates GizmoMemberAccessorImplementor test,
    // but this is making sure the member accessor returned
    // is the one from GizmoMemberAccessorImplementor
    @Test
    void testReturnedMemberAccessor() throws NoSuchMethodException {
        Method member = TestdataEntity.class.getMethod("getValue");
        MemberAccessor memberAccessor =
                GizmoMemberAccessorFactory.buildGizmoMemberAccessor(member, PlanningVariable.class,
                        AccessorInfo.withReturnValueAndNoArguments(), new GizmoClassLoader());

        TestdataEntity entity = new TestdataEntity();
        TestdataValue value = new TestdataValue("A");
        entity.setValue(value);
        assertThat(memberAccessor.executeGetter(entity)).isEqualTo(value);

        assertThat(memberAccessor.supportSetter()).isTrue();

        memberAccessor.executeSetter(entity, null);
        assertThat(entity.getValue()).isNull();

        assertThat(memberAccessor.getDeclaringClass()).isEqualTo(TestdataEntity.class);
        assertThat(memberAccessor.getGenericType()).isEqualTo(member.getGenericReturnType());
        assertThat(memberAccessor.getType()).isEqualTo(member.getReturnType());
        assertThat(memberAccessor.getName()).isEqualTo("value");
        assertThat(memberAccessor.getSpeedNote()).isEqualTo("Fast access with generated bytecode");
    }

    @Test
    void testGizmoNotOnClasspathThrowsException() throws NoSuchMethodException {
        Member member = TestdataEntity.class.getMethod("getValue");
        Thread.currentThread().setContextClassLoader(new ClassLoader() {
            @Override
            public String getName() {
                return "ClassLoader without Gizmo";
            }

            @Override
            public Class<?> loadClass(String name) {
                return null;
            }
        });

        assertThatCode(() -> {
            GizmoMemberAccessorFactory.buildGizmoMemberAccessor(member, PlanningVariable.class,
                    AccessorInfo.withReturnValueAndNoArguments(),
                    new GizmoClassLoader());
        }).hasMessage("When using the domainAccessType (GIZMO) the classpath or modulepath must contain " +
                "io.quarkus.gizmo:gizmo2.\nMaybe add a dependency to io.quarkus.gizmo:gizmo2.");
    }
}
